Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 6 Vererbung, Polymorphie und Schnittstellen
  gp 6.1 Basisklassen und abgeleitete Klassen
    gp 6.1.1 Ableiten einer Klasse
    gp 6.1.2 Klassen, die nicht vererben können
    gp 6.1.3 Zusammenfassung
  gp 6.2 Konstruktoren in abgeleiteten Klassen
    gp 6.2.1 Die Konstruktoren der Klasse »GraphicCircle«
    gp 6.2.2 Der Zugriffsmodifizierer »protected«
    gp 6.2.3 Konstruktorverkettung
    gp 6.2.4 Destruktor-Verkettung
    gp 6.2.5 Der Stand des Projekts »CircleApplication«
    gp 6.2.6 Zusammenfassung
  gp 6.3 Die Methoden in einer abgeleiteten Klasse
    gp 6.3.1 Geerbte Methoden mit »new« verdecken
    gp 6.3.2 Überladen einer Basisklassenmethode
  gp 6.4 Ereignisse in der Vererbung
  gp 6.5 »Hat-eine«-Beziehungen (Aggregation)
    gp 6.5.1 Weiterleitung einer internen Objektreferenz
    gp 6.5.2 Verbergen des internen Objekts
    gp 6.5.3 Innere Klassen
  gp 6.6 Typumwandlung von Objektvariablen
    gp 6.6.1 Die implizite Typumwandlung von Objektreferenzen
    gp 6.6.2 Die explizite Typumwandlung von Objektreferenzen
    gp 6.6.3 Zusammenfassung
  gp 6.7 Abstrakte Klassen und Methoden
    gp 6.7.1 Abstrakte Definitionen
  gp 6.8 Polymorphie
    gp 6.8.1 Virtuelle Methoden
    gp 6.8.2 Inhomogene Mengen
    gp 6.8.3 Verdecken und Überschreiben geerbter Methoden
    gp 6.8.4 Überschreiben der Methode »ToString()« der Klasse »Object«
    gp 6.8.5 Versiegelte Methoden
    gp 6.8.6 Zusammenfassung
  gp 6.9 Erweiterung der Klassenhierarchie »CircleApplication«
    gp 6.9.1 Die Klasse »GeometricObject«
  gp 6.10 Schnittstellen
    gp 6.10.1 Einführung in die Schnittstellen
    gp 6.10.2 Schnittstellendefinition
    gp 6.10.3 Schnittstellenimplementierung
    gp 6.10.4 Typumwandlung mit dem »as«-Operator
    gp 6.10.5 Abstrakte Klassen vs. Schnittstellen
    gp 6.10.6 Zusammenfassung


Galileo Computing

6.5 »Hat-eine«-Beziehungen (Aggregationdowntop

Stellen Sie sich vor, Sie müssten die Klasse eines Motorflugzeugs modellieren. Eine Klasse Flugzeug könnte die Zustandsdaten bereitstellen, beispielsweise Spannweite, AnzahlDerSitzplaetze und MinGeschwindigkeit, die in einem Konstruktor initialisiert werden. Die Implementierung der Klasse könnte wie folgt lauten:


class Flugzeug {
  private float spannweite;
  private int anzahlDerSitzplaetze;
  private float minGeschwindigkeit;
  public Flugzeug(float Spannweite, int Sitzplaetze, 
      float MinGeschwindigkeit) {
    spannweite = Spannweite;
    anzahlDerSitzplaetze = Sitzplaetze;
    minGeschwindigkeit = MinGeschwindigkeit;
  }
  // weiterer Klassencode
}

Da es sich um die Beschreibung eines Motorflugzeugs handelt, sollten wir auch noch eine Klasse Triebwerk entwickeln. Die Objektdaten werden im Konstruktor initialisiert, außerdem enthält der Entwurf zwei Methoden, mit denen das Triebwerk angelassen und abgeschaltet werden kann.


class Triebwerk {
  private int leistung;
  private float gewicht;
  public Triebwerk(int Leistung, float Gewicht) {
    leistung = Leistung;
    gewicht = Gewicht;
  }
  // das Triebwerk starten
  public void Starten() {
    Console.WriteLine("Das Triebwerk wird gestartet");
  }
  // das Triebwerk ausschalten
  public void Ausschalten() {
    Console.WriteLine("Das Triebwerk wird abgeschaltet");
  }
  // weiterer Klassencode
}

In der realen Welt stehen Objekte dieser beiden Klassen miteinander in einer Beziehung, die nicht durch eine Vererbungslinie abgebildet werden kann. Denken Sie daran, dass die Implementierungsvererbung eine Beziehung zwischen Typen beschreibt, die als Ist-eine-Beziehung bezeichnet wird. Dass ein Flugzeug aber kein Triebwerk ist oder umgekehrt ein Triebwerk kein Flugzeug, leuchtet ein.

Um die beiden miteinander in einer unzertrennlichen Beziehung stehenden Typen Flugzeug und Triebwerk zu beschreiben, müsste man sagen, dass ein Flugzeug ein Triebwerk hat. Der vorliegende Sachverhalt einer Hat-eine-Beziehung – auch als Aggregation bezeichnet – wird auch im objektorientierten Sprachgebrauch zum Ausdruck gebracht. Ein Flugzeug hat ein Triebwerk, folgerichtig muss die Klassendefinition Flugzeug eine Instanzvariable vom Typ Triebwerk enthalten. Die Klasse Flugzeug kann mit dieser Überlegung folgendermaßen verbessert werden:


private class Flugzeug {
  private Triebwerk myMotor;
  ...
}

In der übergeordneten Klasse Flugzeug ist ein untergeordnetes Objekt vom Typ Triebwerk privat deklariert. Diese Kapselung entspricht den Paradigmen der objektorientierten Programmierung, wirft jedoch auch sofort die Frage auf, wie ein Objekt vom Typ Triebwerk instanziiert und später gestartet bzw. ausgeschaltet werden kann.

Um bei der Instanziierung eines Flugzeugobjekts sofort eine gültige Referenz auf ein Triebwerkobjekt zu erhalten, eignet sich der Konstruktor der Klasse Flugzeug, in dem die Klasse Triebwerk instanziiert und mit den notwendigen Initialisierungsdaten versorgt wird:


class Flugzeug {
  private Triebwerk myMotor;
  ...
  // aktualisierter Konstruktor der Klasse Flugzeug
  public Flugzeug(float Spannweite, 
                  int Sitzplaetze, 
                  float MinGeschwindigkeit,
                  int Leistung,
                  float Gewicht) {
    spannweite = Spannweite;
    anzahlDerSitzplaetze = Sitzplaetze;
    minGeschwindigkeit = MinGeschwindigkeit;
    myMotor = new Triebwerk(Leistung, Gewicht);
  }
  // weiterer Klassencode
}

Nun beschreibt ein Objekt vom Typ Flugzeug ein ganz bestimmtes Objekt vom Typ Triebwerk – das Triebwerk ist dem Flugzeug eindeutig zugeordnet. Was bleibt, ist das Starten und Ausschalten des Triebwerks. Dazu bieten sich zwei Alternativen an:

1.  Die Definition einer Eigenschaft, welche die Referenz auf das Triebwerk zurückliefert. Auf diese Referenz können anschließend die triebwerksspezifischen Methoden Starten und Ausschalten aufgerufen werden.
2. Die Definition einer Methode in der Klasse Flugzeug, die den Methodenaufruf an das untergeordnete Triebwerkobjekt weiterleitet.
       

Galileo Computing

6.5.1 Weiterleitung einer internen Objektreferenz  downtop

Sehen wir uns zuerst die Realisierung mittels einer Eigenschaft an, welche die in der privaten Variablen myMotor des Flugzeugobjekts enthaltene Referenz eines Triebwerkobjekt dem Aufrufer zur Verfügung stellt. Auf den set-Accessor kann verzichtet werden, weil das Triebwerkobjekt bereits im Konstruktor erzeugt wird:


class Flugzeug {
  private Triebwerk myMotor;
  public Triebwerk Motor {
    get {return myMotor;}
  }
  // weiterer Klassencode
}

Der Benutzer besorgt sich nach der Instanziierung der Klasse Flugzeug die Referenz auf das interne Triebwerkobjekt und kann darauf die beiden Methoden Anlassen und Ausschalten aufrufen:


Flugzeug myCessna = new Flugzeug(8, 4, 80, 150, 300);
myCessna.Motor.Starten();
...
myCessna.Motor.Ausschalten();

Der Teilausdruck


myCessna.Motor

liefert die Referenz auf das Triebwerk, über die mit der üblichen Punktnotation auf alle Klassenmitglieder des internen Objekts zugegriffen werden kann. Festzustellen ist, dass dem Aufrufer nicht verborgen bleibt, dass ein zweites internes Objekt der übergeordneten Klasse seine Dienste bereitstellt.


Galileo Computing

6.5.2 Verbergen des internen Objekts  downtop

Betrachten wir nun die zweite Alternative. In der Klasse Flugzeug sind dazu zwei Methoden definiert: die erste, um das Triebwerk einzuschalten, die zweite, um es wieder auszuschalten. Der Aufruf wird in den Methoden an das interne Triebwerksobjekt weitergeleitet.


class Flugzeug {
  private Triebwerk myMotor;
  ...
  public void MotorStarten() {
    myMotor.Starten();
  }
  public void MotorAusschalten() {
    myMotor.Ausschalten();
  }
}

Der Aufruf einer der beiden Instanzmethoden MotorStarten oder MotorAusschalten des Flugzeug-Objekts bewirkt, dass das Triebwerk das gewünschte Verhalten zeigt: Es wird angelassen oder abgeschaltet. Im Gegensatz zur Weiterleitung der internen Objektreferenz bemerkt der Aufrufer nicht, dass das Flugzeug-Objekt sich hinterlistig der Funktionalität einer zweiten Klasse bedient. Setzen wir voraus, dass die Codeimplementierung der beiden Methoden in der untergeordneten Klasse Triebwerk möglicherweise sehr komplex ist, gaukelt diese Lösung dem Aufrufer Intelligenz vor und verbirgt dabei den tatsächlichen Dienstanbieter – ganz im Gegensatz zu der zuerst vorgestellten Variante, bei der die Referenz des Triebwerk-Objekts dem Aufrufer bekannt ist.

Fassen wir an dieser Stelle den Programmcode, der die beiden Möglichkeiten der Implementierung einer Hat-eine-Beziehung beschreibt, zusammen. Die Klasse Flugzeug im folgenden Beispiel bietet Alternativen an, die in der Main-Methode auch beide benutzt werden. Dabei bedient sich das Objekt myCessna der weitergeleiteten Triebwerkreferenz, das Objekt myPiper hingegen ruft die Methoden MotorStarten und MotorAusschalten auf, ohne direkt das interne Objekt vom Typ Triebwerk anzusprechen.


// ------------------------------------------------------------
// Beispiel: ...\Kapitel 6\HatEineBeziehung 
// ------------------------------------------------------------
class Program {
  static void Main(string[] args) {
    Flugzeug myCessna = new Flugzeug(8,4,80,150,300);
    Console.WriteLine("Der Typ Cessna:");
    myCessna.Motor.Starten();
    myCessna.Motor.Ausschalten();
    Console.WriteLine("------------------------------");
    Flugzeug myPiper = new Flugzeug(9,3,75,180,250);
    Console.WriteLine("Der Typ Piper:");
    myPiper.MotorStarten();
    myPiper.MotorAusschalten();
    Console.ReadLine();
  }
}
// ----------- Klasse Flugzeug -----------
class Flugzeug {
  private float spannweite;
  private int anzahlDerSitzplaetze;
  private float minGeschwindigkeit;
  private Triebwerk myMotor;  
  public Flugzeug(float Spannweite, int Sitzplaetze, 
                  float MinGeschwindigkeit, int Leistung,
                  float Gewicht) {
    spannweite = Spannweite;
    anzahlDerSitzplaetze = Sitzplaetze;
    minGeschwindigkeit = MinGeschwindigkeit;
    myMotor = new Triebwerk(Leistung, Gewicht);
  }
  // die Eigenschaft Motor liefert eine Referenz auf das 
  // interne Objekt myMotor an den Aufrufer 
  public Triebwerk Motor {
    get {return myMotor; }
  }
  // MotorAnlassen und MotorAusschalten rufen die entsprechenden
  // Methoden des internen Objekts myMotor auf
  public void MotorStarten() {
    myMotor.Starten();
  }
  public void MotorAusschalten() {
    myMotor.Ausschalten();
  }
}
// ---------- Klasse Triebwerk -----------
class Triebwerk {
  private int leistung;
  private float gewicht;
  public Triebwerk(int Leistung, float Gewicht) {
    leistung = Leistung;
    gewicht = Gewicht;
  }
  // das Triebwerk anlassen
  public void Starten() {
    Console.WriteLine("Der Motor wird gestartet");
  }
  // das Triebwerk ausschalten
  public void Ausschalten() {
    Console.WriteLine("Der Motor wird abgeschaltet");
  }
}


Galileo Computing

6.5.3 Innere Klassen  toptop

Es gibt noch eine andere Möglichkeit, Hat-eine-Beziehungen zu realisieren. Dazu wird im Gültigkeitsbereich einer Klasse eine weitere Klasse definiert:


public class ClassA {
   public class ClassB {/*...*/}
   /*...*/
}

Eine Klasse, die innerhalb einer anderen Klasse definiert ist, wird als innere Klasse bezeichnet. Im Codefragment ist das die Definition der Klasse ClassB in ClassA. Die Verwendung innerer Klassen ist eine bequeme Möglichkeit zur Bereitstellung untergeordneter Objekte und wird benutzt, wenn ein Typ einem anderen Typ logisch zugeordnet werden kann.

Rufen Sie sich noch einmal das Beispiel des Flugzeugs und des Triebwerks in Erinnerung, deren Klassen wir weiter oben folgendermaßen definiert hatten:


class Flugzeug {/*...*/}
class Triebwerk {/*...*/}

Ein Triebwerk ist Bauteilkomponente eines Flugzeugs. Wenn kein Zwang dazu besteht, ein Triebwerk auch als separates Objekt einer Betrachtung zu unterziehen, wäre es überlegenswert, die Definition Triebwerk in der Definition Flugzeug einzuschließen:


public class Flugzeug {
  public class Triebwerk {
    // Anweisungen
  }
}

Ein Objekt vom Typ Triebwerk ist jetzt abhängig von einem Objekt des Typs Flugzeug und kann nur im Kontext der äußeren Klasse, also von Flugzeug, benutzt werden.

Ist die innere Klasse public deklariert, muss bei der Instanziierung immer der Typ der äußeren Klasse angegeben werden.


Flugzeug.Triebwerk turbine = new Flugzeug.Triebwerk(545, 320);

Die eindeutige Zugehörigkeit eines Triebwerk-Objekts zu einem Flugzeug zeigt sich erst, wenn das Flugzeug-Objekt ein ihm zugeordnetes Triebwerk hat. Dazu wird im Konstruktor der äußeren Klasse die innere Klasse instanziiert und die Referenz in einer gekapselten Variablen vorgehalten.


public class Flugzeug {
  private Triebwerk turbine;
  public Flugzeug() {
    turbine = new Triebwerk();
    // Anweisungen
  }
  public class Triebwerk {/*...*/}
}

Festhalten können wir, dass sich im Vergleich zu zwei separaten Klassendefinitionen, wie wir sie anfangs hatten, nicht wesentlich viel geändert. Interessant wird es, wenn die innere Klasse private deklariert wird:


public class Flugzeug {
  ...
  private class Triebwerk {/*...*/}
}

Die Klasse Triebwerk ist nun ein eindeutig einem Flugzeugobjekt zugeordnetes Element. Kein anderer Code, außer der in der äußeren Klasse, hat Zugriff auf den inneren Typ.

Doch wie kann ein Aufrufer ein Triebwerk starten, wenn der Typ Triebwerk für ihn völlig unsichtbar bleibt und er deshalb nicht die klassenspezifischen Methoden aufrufen kann? Die Lösung führt wieder über Methoden, die in der äußeren Klasse Flugzeug definiert sind:


public class Flugzeug {
  private Triebwerk turbine = new Triebwerk(200, 56);
  ...
  public void TurbineStarten() {
    turbine.Starten();
  }
  // innere Klasse
  private class Triebwerk {
    ...
     public void Starten() {
       Console.WriteLine("Die Turbine wird gestartet");
     }
  }
}

Weil die Klasse Triebwerk jetzt private deklariert ist, hat sie eine Sichtbarkeit wie jede andere private Entität der äußeren Flugzeug-Klasse. Ein Flugzeug-Objekt kann deshalb auch die innere, private Klasse instanziieren. Der resultierende Verweis wird in der Methode TurbineStarten zum Aufruf der Methode Starten benutzt. Obwohl der Aufrufer nichts von der Existenz der Klasse Triebwerk weiß, wird er Nutzen aus dieser Klasse ziehen können und die Reaktion auf den Aufruf zu spüren bekommen – und sei es nur die Ausgabe im Konsolenfenster.

Die gegenseitige Abhängigkeit von äußerer und innerer Klasse

Wir wollen nun unsere Überlegungen hinsichtlich der Abhängigkeit der beiden Klassen Flugzeug und Triebwerk auf die Spitze treiben. Wenn wir es ganz streng sehen, müssen wir feststellen, dass ein Flugzeug ein Triebwerk besitzt und ein Triebwerk auch in einem ganz bestimmten Flugzeug eingebaut ist. Deklarieren wir die innere Klasse Triebwerk public, widerspricht das der Anweisung zur Instanziierung der Triebwerk-Klasse, die wir oben noch mit


Flugzeug.Triebwerk turbine = new Flugzeug.Triebwerk(545, 320);

durchgeführt haben. Woher weiß das Triebwerk, in welchem Flugzeug es eingebaut ist? Es hat keine Information darüber, die diese Zuordnung beschreibt.

Um den Kreis zu schließen und Eindeutigkeit zu gewährleisten, müssen wir dem Konstruktoraufruf in der Klasse Triebwerk die Referenz auf das Flugzeug-Objekt übergeben, dem das Triebwerk zugeordnet ist. Diese wird im privaten Feld flugzeug der Klasse Triebwerk gespeichert. Ferner ist in der überarbeiteten Klasse die Methode TriebwerkStarten definiert, aus der heraus zu Demonstrationszwecken die Methode Starten des Flugzeugobjekts aufgerufen wird.

Sehen wir uns nun die Klassendefinition von Flugzeug an, die sich auf das Wesentlichste beschränkt. Damit wir uns später im Testcode vom Erfolg überzeugen können, wird ein Flugzeug durch einen Namen beschrieben, der dem Konstruktor der Klasse Flugzeug bei der Instanziierung übergeben wird. Außerdem wird die Eigenschaft Motor mit der Rückgabe der Referenz auf das Triebwerkobjekt angeboten, über die ein Benutzer Zugriff auf die Methoden des Triebwerks hat.


public class Flugzeug {
  private Triebwerk motor;
  private string name;
  // Konstruktor
  public Flugzeug(string bezeichnung) {
    motor = new Triebwerk(this);
    this.name = bezeichnung;
  }
  // Eigenschaft, welche die Referenz auf das interne Triebwerk-
  // objekt liefert
  public Triebwerk Motor {
    get {return this.motor;}
  }
  public void Starten() {
    Console.WriteLine("Das Flugzeug {0} startet", this.name);
  }
  // ------------ innere Klasse Triebwerk ---------------
  public class Triebwerk {
    private Flugzeug flugzeug;
    // Konstruktor
    public Triebwerk(Flugzeug flg) {
      this.flugzeug = flg;
      this.flugzeug.motor = this;
    }
    public void TriebwerkStarten() {
      Console.WriteLine("Das Triebwerk wird gestartet");
      flugzeug.Starten();
    }
  }
}

Wir können nun die Klasse Flugzeug instanziieren und auf dessen Referenz die Eigenschaft Motor aufrufen, die ihrerseits die Referenz auf das dem Flugzeug zugeordnete Triebwerk liefert. Damit können wir das Triebwerk starten:


Flugzeug flg = new Flugzeug("Piper");
flg.Motor.TriebwerkStarten();

An der Konsole erhalten wir daraufhin die Ausgabe:


Das Triebwerk wird gestartet
Das Flugzeug Piper startet

Wir haben auch die Möglichkeit, die innere Klasse Triebwerk zu instanziieren. Dabei müssen wir dem Konstruktor die Referenz auf ein konkretes Flugzeug-Objekt übergeben:


Flugzeug flg = new Flugzeug("Piper");
Flugzeug.Triebwerk trb = new Flugzeug.Triebwerk(flg);

Grundsätzlich birgt dieser Aufruf die Gefahr der Inkonsistenz, denn wir erzeugen ein neues Triebwerk auf Basis des konkreten Flugzeugs flg. Dieses Flugzeugobjekt ist aber bereits im Besitz eines Triebwerks, das im Konstruktor erzeugt worden ist und dessen Referenz im Feld motor vorgehalten wird. Wir müssen dem Flugzeugobjekt mitteilen, dass das erste Triebwerk »ausgetauscht« und durch ein neues ersetzt wird. Um dem Flugzeug die Existenz des neuen Triebwerks mitzuteilen, enthält der Konstruktor der Klasse Triebwerk die Anweisung


this.flugzeug.motor = this;

Nun ist der Kreis vollständig geschlossen. Jedes Triebwerk ist unzweifelhaft einem bestimmten Flugzeug zugeordnet, und jedes Flugzeug hat auch nur ein bestimmtes Triebwerk.

Die Zugriffsmodifizierer einer inneren Klasse

Wie Sie oben gesehen haben, unterscheidet sich eine öffentlich (public oder internal) deklarierte innere Klasse nicht wesentlich von einer separaten Klassendefinition. Ein Benutzer »sieht« die innere Klasse und kann sie instanziieren. Das Konzept der verschachtelten Klassen, nämlich die konkrete Zugehörigkeit zu einem Objekt der umschließenden Klasse, wird aufgeweicht.

Ganz anders verhält sich eine private deklarierte innere Klasse. Die Sichtbarkeit ist nur auf das umschließende äußere Objekt begrenzt, die Bindung ist eindeutig und kann nicht aufgebrochen werden. Erst jetzt kann der Verbund zwischen dem vom Aufrufer erkennbaren Objekt und seinem zugeordneten Teilobjekt die konzeptuellen Stärken voll ausspielen.

Ist die umschließende, äußere Klasse nicht sealed definiert, kann sie abgeleitet werden. Normalerweise dürfte die ableitende Klasse gleichermaßen Interesse an einem Objekt der eingebetteten Klasse haben. Weil sich private deklarierte Komponenten jedoch der Sichtbarkeit der ableitenden Klasse entziehen, sollte die eingebettete Klasse in einer ableitbaren äußeren immer protected deklariert sein.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de